#include "StdAfx.h"
#include "Audio.h"
#include "Counter.h"
#include "Globals.h"

//-----------------------------------------------------------------------------
Audio::Audio()
:	pXAudio2			(NULL),
	pMasteringVoice(NULL)
{
	HRESULT hr;
	
// Initialize XAudio2
	hr = XAudio2Create(&pXAudio2, 0);

	LOG("XAudio2Create = " << hr);

// Create a mastering voice
	if(hr == S_OK)
	{
		hr = pXAudio2->CreateMasteringVoice(&pMasteringVoice);
		LOG("CreateMasteringVoice = " << hr);
		if(hr != S_OK)
			Error(L"Couldn't create mastering voice.");
		else
			PTT::UseAudio = true;
	}
	else
		Error(L"Failed to initialize XAudio2.\nPlease update DirectX.");
}

//-----------------------------------------------------------------------------
Audio::~Audio()
{
	if(pMasteringVoice)
		pMasteringVoice->DestroyVoice();

	if(pXAudio2)
		pXAudio2->Release();
}

//-----------------------------------------------------------------------------
IXAudio2SourceVoice *Audio::newVoice(WORD channels, WORD bitsPerSample, DWORD sampleRate, IXAudio2VoiceCallback *callback)
{
	if(pXAudio2 == NULL)
		return NULL;

// Set pcm format
	WORD	blockAlign		= channels * bitsPerSample / 8;
	DWORD	bytesPerSecond	= blockAlign * sampleRate;
	WAVEFORMATEX wfx = {WAVE_FORMAT_PCM, channels, sampleRate, bytesPerSecond, blockAlign, bitsPerSample, 0};

// Create new voice
	IXAudio2SourceVoice *voice = NULL;

	if(channels && bitsPerSample && sampleRate)
		pXAudio2->CreateSourceVoice(&voice, &wfx, 0, 2.0f, callback);

	return voice;
}







//-----------------------------------------------------------------------------
BufferedVoice::BufferedVoice(Audio *audioRef, DWORD numBuffers, float vol, bool block_buffers)
:	buffers			(numBuffers),
	voices			(0),
	audio				(audioRef),
	channels			(0),
	bitsPerSample	(0),
	sampleRate		(0),
	volume			(vol),
	blockBuffers	(block_buffers),
	state				(stopped),
	threadHandle	(NULL)
{
	memset(&buffers[0], 0, numBuffers*sizeof(XAUDIO2_BUFFER));
	InitializeCriticalSectionAndSpinCount(&state_access, 800);
}


//-----------------------------------------------------------------------------
BufferedVoice::~BufferedVoice()
{
//	wait for fill buffer thread to exit
	while(threadHandle)
	{
		state = exit;
		Sleep(1);
	}

	for(unsigned i = 0; i < voices.size(); i++)
		voices[i]->DestroyVoice();

	DeleteCriticalSection(&state_access);
}

//-----------------------------------------------------------------------------
void BufferedVoice::destroyVoices()
{
	EnterCriticalSection(&state_access);
	{
		state = stopped;

		for(unsigned i = 0; i < voices.size(); i++)
			voices[i]->DestroyVoice();

		voices.resize(0);
	}
	LeaveCriticalSection(&state_access);
}

//-----------------------------------------------------------------------------
DWORD WINAPI BufferedVoice::FillBufferThread(LPVOID lpParam)
{
// Initialize the COM library for use by this thread
	HRESULT result = CoInitializeEx(NULL, COINIT_MULTITHREADED);

	BufferedVoice *our = (BufferedVoice*) lpParam;

	LOG("Created new FillBufferThread, CoInitializeEx = " << result << ", BufferedVoice = " << our);

	our->threadHandle	= GetCurrentThread();

	SetThreadPriority(our->threadHandle, THREAD_PRIORITY_ABOVE_NORMAL);

	Counter timer;
	unsigned i = 0;							// buffer index

	while(true)
	{
	//----
	//	check for exit condition
		if(our->state == exit)
			break;

		bool bufferFilled = false;

	//----
	//	We don't hog the critical section: only try to fill one buffer,
	//	before giving other threads the chance to change the state.
		if(PTT::UseAudio)
		if(TryEnterCriticalSection(&our->state_access))
		{
			if(our->state != stopped)
			{
				if(i >= our->buffers.size())
					i = 0;

			//----
			//	Fill and submit marked buffers
				UINT32 flags = our->buffers[i].Flags;
				if((flags & FILL_AND_SUBMIT_BUFFER) && !(our->blockBuffers && (flags & BLOCK_BUFFER)))
				{
					if(our->fillBuffer(i))
					{
						our->submitBuffer(i);
						bufferFilled = true;
					}
				}

				i++;
			}
			else
				i = 0;

		//----
		//	change volume for fade-in or fade-out (only works on the first voice)
			if(our->voices.size())
			{
				double t = timer.getAndReset();
				float volume;
				our->voices[0]->GetVolume(&volume);
				if(volume >= our->volume
				&&	our->state == fade_in)
					our->state = playing;
				else if(volume <= 0.0f
				&&	our->state == fade_out)
					our->state = playing;	// continue playing at 0 volume

			// fade in and out will take about a second.
				else if(our->state == fade_in)	our->voices[0]->SetVolume(min(our->volume, volume+0.02f*our->volume));
				else if(our->state == fade_out)	our->voices[0]->SetVolume(max(0.0f,        volume-0.02f*our->volume));
			}

			LeaveCriticalSection(&our->state_access);
		}

	//----
	//	Nothing to do? -> sleep a little longer.
		if(!bufferFilled)
		{
		//----
		//	check for exit condition again
			if(our->state == exit)
				break;

			Sleep(10);
		}
		else
			Sleep(1);

	}//while loop

	our->threadHandle = NULL;				// signal destructor, that we're finished

	CoUninitialize();

	return 0;

}//BufferedVoice::FillBufferThread

//-----------------------------------------------------------------------------
void BufferedVoice::createFillBufferThread()
{
	CreateThread(NULL, 0, FillBufferThread, this, 0, NULL);
}

//-----------------------------------------------------------------------------
void BufferedVoice::exitFillBufferThread()
{
	EnterCriticalSection(&state_access);
	state = exit;
	LeaveCriticalSection(&state_access);
}

//-----------------------------------------------------------------------------
void BufferedVoice::submitBuffer(unsigned buffer, unsigned voice)
{
	if(buffer > buffers.size() || voice > voices.size())
		return;

//----
//	reset flags
	buffers[buffer].Flags &= ~FILL_AND_SUBMIT_BUFFER;

// store buffer as context
	buffers[buffer].pContext = (void*)buffer;

	if(PTT::UseAudio)
		voices[voice]->SubmitSourceBuffer(&buffers[buffer]);
}

//-----------------------------------------------------------------------------
void BufferedVoice::initialize(WORD c, WORD b, DWORD s)
{
	channels			= c;
	bitsPerSample	= b;
	sampleRate		= s;

	destroyVoices();
}

//-----------------------------------------------------------------------------
int BufferedVoice::play()
{
	if(!PTT::UseAudio)
		return 0;

//----
//	search for idle voice
	int voice = -1;
	for(unsigned i = 0; i < voices.size(); i++)
	{
		if(BuffersQueued(voices[i])==0)
		{
			voice = i;
			break;
		}
	}

//----
//	need an additional voice?
	if(voice == -1)
	{
		IXAudio2SourceVoice *v = audio->newVoice(channels, bitsPerSample, sampleRate, this);

		if(v==NULL)
			return 0;

		voices.push_back(v);
		voice = voices.size()-1;
	}

//----
//	enque all buffers
	for(unsigned i = 0; i < buffers.size(); i++)
	{
		if(fillBuffer(i))
			submitBuffer(i, voice);
		else
			break;
	}

//----
//	start playback
	state = playing;
	voices[voice]->SetVolume(volume);
	voices[voice]->Start();

	return voice;
}


//-----------------------------------------------------------------------------
void BufferedVoice::pause(int voice)
{
	EnterCriticalSection(&state_access);
	if(state != stopped && state != exit)
		state = paused;
	LeaveCriticalSection(&state_access);

	if(voice >= 0)
		voices[voice]->Stop();
	else
	for(unsigned i = 0; i < voices.size(); i++)
		voices[i]->Stop();
}

//-----------------------------------------------------------------------------
void BufferedVoice::resume(int voice)
{
//----
	int started = 0;
	if(voice >= 0)
	{
		voices[voice]->Start();
		started++;
	}
	else
	{
	//	continue all voices which actually had buffers queued
		for(unsigned i = 0; i < voices.size(); i++)
		{
			if(BuffersQueued(voices[i]))
			{
				voices[i]->Start();
				started++;
			}
		}
	}


	if(started)
	{
		EnterCriticalSection(&state_access);
		state = playing;
		LeaveCriticalSection(&state_access);
	}
}

//-----------------------------------------------------------------------------
void BufferedVoice::stop(int voice)
{
//----
//	set flag, to stop refilling of buffers
	EnterCriticalSection(&state_access);
	state = stopped;
	LeaveCriticalSection(&state_access);

//----
//	stop the voice(s)
	if(voice >= 0)
	{
		voices[voice]->Stop();
		voices[voice]->FlushSourceBuffers();
	}
	else
	{
		for(unsigned i = 0; i < voices.size(); i++)
		{
			voices[i]->Stop();
			voices[i]->FlushSourceBuffers();
		}
	}
}

//-----------------------------------------------------------------------------
void BufferedVoice::setVolume(float vol, int voice)
{
	volume = vol;

	if(voice >= 0)
	{
		voices[voice]->SetVolume(vol);
	}
	else
	{
		for(unsigned i = 0; i < voices.size(); i++)
			voices[i]->SetVolume(vol);
	}
}

//-----------------------------------------------------------------------------
float BufferedVoice::getVolume(unsigned voice) const
{
	float vol = 0;

	if(voice < voices.size())
		voices[voice]->GetVolume(&vol);
		
	return vol;
}

//-----------------------------------------------------------------------------
void BufferedVoice::fadeIn()
{
	if(voices.size())
		voices[0]->SetVolume(0);
	if(state==playing || state==fade_out)
		state = fade_in;
}

//-----------------------------------------------------------------------------
void BufferedVoice::fadeOut()
{
	if(state==playing || state==fade_in)
		state = fade_out;
}
